/*
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.merak.core.text.template;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.merak.core.annotation.Skip;

public class Reflect {
	
    //~ Constants //////////////////////////////////////////////////////////////////////////////////
	//**********************************************************************************************
	private static final Pattern GETTER = Pattern.compile("(get|has|is)([A-Z].*)");
	private static final Pattern SETTER = Pattern.compile("(set)([A-Z].*)");
	
	//~ Methods ////////////////////////////////////////////////////////////////////////////////////
	//**********************************************************************************************
	/** Top-down walk in class taxonomy (depth-first) */
	@SuppressWarnings("rawtypes")
	public static List<Class> listLineage(Class<?> c,List<Class> output) {
		
		// Base Case
		if (c == null         ) return output;
		if (c == Object.class ) return output;
		if (output.contains(c)) return output;
		
		// Process Super
		listLineage( c.getSuperclass(), output );
		
		// Process Interfaces
		for (Class<?> i : c.getInterfaces()) {
			listLineage(i, output);	
		}

		// Process This
		output.add(c);
		return output;

	}
	//**********************************************************************************************
	/** List of distinct methods in same order classes appear in the parameter list.  Distinct means 
	 *  that overridden methods (same signature) are replaced by the most specific method only. */
	@SuppressWarnings("rawtypes")
	public static List<Method> listDistinctMethods(List<Class> list) {
		
		List<Method>       output   = new ArrayList<Method>();
		Map<String,Method> visited  = new HashMap<String,Method>();
		Method             previous = null;
		
		for (Class<?> c : list) {
			for (Method m : c.getDeclaredMethods()) {
				// Ignore methods with non-specific skip annotation
				if (m.isAnnotationPresent(Skip.class)){
					if (m.getAnnotation(Skip.class).what().length == 0) continue;
				}
				// Ignore non-public methods
				if (!Modifier.isPublic(m.getModifiers())) continue;			
				// Replace overridden methods
				previous = visited.get(m.getName());
				if (overrides(m,previous)) {
					output.set(output.indexOf(previous), m);					
				}
				else {
					output.add(m);					
				}
				visited.put(m.getName(),m);
			}
		}
		return output;

	}
	//**********************************************************************************************
	/** List of distinct methods found in the lineage of class c (top-down) */
	@SuppressWarnings("rawtypes")
	public static List<Method> listDistinctLineageMethods(Class<?> c) {

		return listDistinctMethods(
				listLineage(c,new LinkedList<Class>())
		);

	}
	//****************************************************************************
	public static Matcher matchGetter(Method m) {
		
		// Auxiliary
		String   name       = m.getName();
		Class<?> returnType = m.getReturnType();		
		Matcher  matcher    = GETTER.matcher(name);
		
		// Must have no arguments
		boolean isGetter = (m.getParameterTypes().length == 0)
		
		// Must start with 'get', 'is' or 'has' followed by name
		&& ( matcher.matches() )
		
		// Must return something different of Void or Class
		&& (returnType != Void.class)
		&& (returnType != void.class)
		&& (returnType != Class.class);

		// Must return boolean if starts with 'is' or 'has'
		if (name.startsWith("is") || name.startsWith("has")) {
			isGetter &= returnType==boolean.class || returnType==Boolean.class;
		}
		
		// Register it if a getter	
		return isGetter ? matcher : null;
		
	}
	//****************************************************************************
	public static Matcher matchSetter(Method m) {
		
		// Auxiliary
		String   name       = m.getName();
		Class<?> returnType = m.getReturnType();		
		Matcher  matcher    = SETTER.matcher(name);
		
		// Must start with 'set' or 'has' followed by capital word
		boolean isSetter = matcher.matches()

		// Must have only 1 argument
		&& (m.getParameterTypes().length == 1)
				
		// Must have no Skip annotation
		&& (!m.isAnnotationPresent(Skip.class));
		
		// Must receive boolean if starts with 'has'
		if (name.startsWith("has")) {
			isSetter &= returnType==boolean.class || returnType==Boolean.class;
		}
		
		// Register it if a setter
		return isSetter ? matcher : null;
		
	}
	//**********************************************************************************************
	/** True if m1 overrides m2. */
	public static boolean overrides(Method m1,Method m2) {
		
		return m1!=null 
			&& m2!=null
			&& m1.getName().equals(m2.getName())
			&& m2.getDeclaringClass().isAssignableFrom(m1.getDeclaringClass())
		    && m2.getReturnType().isAssignableFrom(m1.getReturnType()) 
		    && Arrays.equals(m1.getParameterTypes(), m2.getParameterTypes()); 

	}	
	//**********************************************************************************************
}
